5w 字 | 172 图 | 超级赛亚级 Spring Cloud 实战
这是我的第 70 篇原创文章
作者 | 悟空聊架构
来源 | 悟空聊架构(ID:PassJava666)
一、PassJava 项目简介
需要 PDF 的同学 公众号回复 【pdf】。
PassJava 是一款 Java 面试刷题
的开源系统,可以用零碎时间利用小程序查看常见面试题,夯实 Java 基础。PassJava 项目可以教会你如何搭建 SpringBoot 项目,Spring Cloud 项目 采用流行的技术,如 SpringBoot、MyBatis、Redis、 MySql、 MongoDB、 RabbitMQ、Elasticsearch,采用 Docker 容器化部署。
项目地址
[后台平台] https://github.com/Jackson0714/PassJava-Platform [后台管理] https://github.com/Jackson0714/PassJava-Portal [学习教程] https://github.com/Jackson0714/PassJava-Learning
项目演示
后台管理系统
小程序
PassJava 中使用的技术
SpringBoot、MyBatis、Redis、 MySql、 MongoDB、 RabbitMQ、Elasticsearch
PassJava 实现的功能概览
PassJava 数据库表概览
数据库表前缀说明
ums_*:会员模块相关表 cms_*:内容管理模块相关表 qms_*:题目模块相关表 chms_*:渠道模块相关表 sms_*:学习模块相关表
二、项目微服务架构图
微服务架构图
三、项目前置要求
“由于 PassJava 项目涉及到很多知识点,希望大家先补下功课,推荐的书籍如下。
”
推荐资料
IDEA
《IntelliJ-IDEA-Tutorial》:https://github.com/judasn/IntelliJ-IDEA-Tutorial
Spring
《Spring 实战(第 4 版)》:https://book.douban.com/subject/26767354/
SpringBoot
《Spring Boot 实战》:https://book.douban.com/subject/26857423/
MyBatis
《MyBatis 从入门到精通》:https://book.douban.com/subject/27074809/
MySql
《深入浅出 MySQL》:https://book.douban.com/subject/25817684/
Linux
《循序渐进 Linux(第 2 版)》:https://book.douban.com/subject/26758194/
Elasticsearch
《Elasticsearch 权威指南》:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
《Elasticsearch 技术解析与实战》:https://book.douban.com/subject/26967826/
Mongodb
《MongoDB 实战 (第二版)》:https://book.douban.com/subject/27061123/
Docker
《Spring Cloud 与 Docker 微服务架构实战》:https://book.douban.com/subject/27028228/
四、环境搭建篇
4.1 Vagrant 快速搭建 Ubuntu 虚拟机环境
1. 开启虚拟机服务
Windows 启动配置:Intel Virtualization Technology -> Enabled
2. 下载安装 VirtualBox
VirtualBox:虚拟机管理软件
https://www.virtualbox.org/wiki/Downloads
3. 下载安装 Vagrant
Vagrant:创建和管理虚拟机
Vagrant 软件:https://www.vagrantup.com/downloads.html
Vagrant 官方镜像:https://app.vagrantup.com/boxes/search
check 是否安装好了 vagrant
命令行输入 vagrant
vagrant
4. 安装 vagrant ubuntu 国内镜像
# ubuntu 18.04 LTS:
vagrant box add https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/bionic/current/bionic-server-cloudimg-amd64-vagrant.box --name ubuntu18
# ubunt 16.04 LTS:
vagrant box add https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/xenial/current/xenial-server-cloudimg-amd64-vagrant.box --name ubuntu16
# ubuntu14:
vagrant box add https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box --name ubuntu14
安装 ubuntu 18
vagrant box add https://mirrors.tuna.tsinghua.edu.cn/ubuntu-cloud-images/bionic/current/bionic-server-cloudimg-amd64-vagrant.box --name ubuntu18
创建 vagrant 配置文件
vagrant init
打开 C:\Users\Administrator\Vagrantfile 文件
config.vm.box = "base" 修改为
config.vm.box = "ubuntu18"
5. 启动虚拟机
vagrant up
6. 连接虚拟机
vagrant ssh
7. 配置密码登录
配置密码登录 vagrant
Vagrant ssh 进入系统之后
sudo su
编辑 sshd_config
vi /etc/ssh/sshd_config
PasswordAuthentication no 改为 PasswordAuthentication yes
PermitRootLogin prohibit-password 改为 PermitRootLogin yes
重启服务
service sshd restart
安装 XShell 工具和 XFTP 工具
XShell 连接虚拟机
账号:root
密码:vagrant
4.2 配置虚拟机网络
1.查看VirtualBox Host-Only Network
本地VirtualBox 网络地址 192.168.56.1
,则修改虚拟机IP地址为同一个网段下,比如192.168.56.10
2.配置虚拟机IP地址
打开Vagrant 配置文件 C:\Users\Administrator\Vagrantfile
# config.vm.network "private_network", ip: "192.168.33.10"
修改为
config.vm.network "private_network", ip: "192.168.56.10"
3.重新加载虚拟机
vagrant reload
4.查看虚拟机IP地址
虚拟机IP地址:192.168.56.10,和配置文件中的一致
5.测试本机是否可以ping通虚拟机
ping 192.168.56.10,可以ping通
6.测试虚拟机是否可以ping通本机
ping 192.168.10.160,可以ping通
4.3 安装docker
https://docs.docker.com/engine/install/ubuntu/
1.卸载老版本docker
sudo apt-get remove docker docker-engine docker.io containerd runc
2.设置仓库
// 命令1
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
// 命令2
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
// 命令3
sudo apt-key fingerprint 0EBFCD88
// 命令4
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
3.安装docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
4.测试安装成功
sudo docker run hello-world
5.设置开机自启动
sudo systemctl enable docker
6.配置镜像加速
https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["您的专属加速器地址"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
7. 免sudo使用docker命令
当以普通用户身份去使用docker images时,出现以下错误:
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.26/images/json: dial unix /var/run/docker.sock: connect: permission denied 可以看都,最后告知我们时权限的问题。那么在linux文件权限有三个数据左右drwxrwxrwx,
其中第一为d代表该文件是一个文件夹 前三位、中三位、后三位分别代表这属主权限、属组权限、其他人权限。如图,其中 第三列、第四列分别代表文件的属主、属组。
上图是报错文件的权限展示,可以看到其属主为root,权限为rw,可读可写;其属组为docker,权限为rw,可读可写。如果要当前用户可直接读取该文件,那么我们就为当前用户添加到docker属组即可。
如果还没有 docker group 就添加一个:
sudo groupadd docker
将用户加入该 group 内。然后退出并重新登录就生效啦。
sudo gpasswd -a ${USER} docker
重启 docker 服务
sudo service docker restart
切换当前会话到新 group 或者重启 X 会话
newgrp - docker
注意:最后一步是必须的,否则因为 groups 命令获取到的是缓存的组信息,刚添加的组信息未能生效,所以 docker images 执行时同样有错。
8. apt-get update更新慢
Ubantu 18.04 apt-get update 无法更新,更新慢的问题 https://blog.csdn.net/stopping5/article/details/80493643
sudo cp /etc/apt/sources.list /etc/apt/sources.list.old
sudo vim /etc/apt/sources.list
替换成阿里源
#阿里源:
deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse
其他命令
docker update redis --restart=always 虚拟机重启后,redis自动启动
docker update mysql --restart=always 虚拟机重启后,mysql自动启动
4.4 docker 安装mysql
1.下载镜像
sudo docker pull mysql:5.7
ubuntu@VM-0-13-ubuntu:~$ sudo docker pull mysql:5.7
5.7: Pulling from library/mysql
c499e6d256d6: Pull complete
22c4cdf4ea75: Pull complete
6ff5091a5a30: Pull complete
2fd3d1af9403: Pull complete
0d9d26127d1d: Pull complete
54a67d4e7579: Pull complete
fe989230d866: Pull complete
466a91a95e2f: Pull complete
3e4554c238f1: Pull complete
603b48ead88c: Pull complete
1e86a9aa7171: Pull complete
Digest: sha256:fbaeced79cfdae5d3c8d4a8c41e883f254f72ed7428c6b93a498824b76d97121
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
2.查看下载的镜像
sudo docker images
3.创建mysql实例并启动
创建mysql实例并启动
sudo docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
参数说明
-p 3306:3306 将容器的3306端口映射到主机
-v /mydata/mysql/log:/var/log/mysql\ 将日志文件挂载到主机
-v /mydata/mysql/data:/var/lib/mysql\ 将数据文件挂载到主机
-v /mydata/mysql/conf:/etc/mysql\ 将配置文件挂载到主机
查看docker容器
mysql容器已启动
4.连接数据库
用Workbench连接数据库
查看数据库
5.进入mysql 容器
sudo docker exec -it mysql /bin/bash
6.查看虚拟机映射文件
cd /mydata/mysql
ls
7.修改mysql账号密码
1.进入mysql容器
docker exec -it mysql /bin/bash
2.登录mysql
mysql -u root -p
输入密码:root
3.切换数据库
use mysql
4.查询root用户
select * from user where user = root;
5.修改密码
update user set authentication_string = password('新的密码'), password_expired = 'N', password_last_changed = now() where user = 'root';
6.这条命令暂不清楚
update user set plugin="mysql_native_password";
7.刷新权限
flush privileges;
8.退出
quit;
9.重新登录
mysql -u root -p
输入新的密码,登录成功
8.其他命令
设置容器在机器重启后自动启动
docker update 84c --restart=always
4.5 docker安装redis
1.下载镜像
下载镜像
sudo docker pull redis
ubuntu@VM-0-13-ubuntu:~$ sudo docker pull redis
Using default tag: latest
latest: Pulling from library/redis
c499e6d256d6: Already exists
bf1bc8a5a7e4: Pull complete
7564fb795604: Pull complete
ec6e86f783e4: Pull complete
1371d6223f46: Pull complete
021fd554320f: Pull complete
Digest: sha256:a732b1359e338a539c25346a50bf0a501120c41dc248d868e546b33e32bf4fe4
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
查看下载的镜像
sudo docker images
2.启动redis
创建redis.conf 配置文件
sudo mkdir -p /mydata/redis/conf
sudo touch /mydata/redis/conf/redis.conf
启动redis
sudo docker run -p 6379:6379 --name redis -v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf
3.连接redis
sudo docker exec -it redis redis-cli
4.测试redis
设置a=100,返回OK
set a 100
获取a的值,返回"100"
get a
5.设置redis持久化存储
修改虚拟机映射的redis配置文件
修改配置文件:
sudo vim /mydata/redis/conf/redis.conf
添加配置:
appendonly yes
检查是否生效
重启redis容器:
docker restart redis
设置a=200,返回OK
set a 200
获取a的值,返回"200"
get a
重启redis容器
sudo docker restart redis
重新连接redis容器
sudo docker exec -it redis redis-cli
获取a的值
get a,返回"200"
6.安装redis可视化工具
安装redis可视化工具
redis-desktop-manager
连接redis
查看redis数据库
4.6 本地开发环境配置
1. 本地环境安装Java
我本地环境的java版本 1.8.0_131
java -version
java安装和环境变量配置:https://www.cnblogs.com/jackson0714/p/6591942.html
2.本地环境配置Maven
(1)下载Maven,拷贝文件夹到C盘
C:\apache-maven-3.6.2
(2)添加到环境变量
cmder里面 执行命令 mvn -v
如果报错命令不存在,则重新启动cmder
(3)设置Maven代理
阿里云代理 https://maven.aliyun.com/mvn/view
点击使用指南,拷贝配置指南
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
(4)配置jdk1.8编译项目
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
<profiles>
3.IDEA Maven构建工具配置
Maven配置
字符集配置
4. IDEA 安装Lombok插件
Lombok:简化JavaBean的开发
5. IDEA 安装mybatisx 插件
mybatisx:mybatis plus开发的一个插件,从mapper方法快速定位到xml文件
6.安装VSCode
https://code.visualstudio.com/
7.添加VSCode插件
Auto Close Tag 自动加上关闭标签
Auto Rename Tag 自动命名配对标签
Chinese 中文简体包
ESLint 语法检查
HTML CSS Support 帮助CSS开发
HTML Snippets 帮忙HTML开发
JavaScript (ES6) 帮助JavaScript开发
Liver Server 启动一个本地服务
open in browser 用浏览器打开文件
Vetur 帮助Vue开发
minapp 帮助小程序开发
问题
1.新项目导入main1,main2
删除main1.iml,main2.iml
4.7 配置Git
1.配置git 用户名和邮箱
git config --global user.name "jackson0714"
git config --global user.email "jackson0585@163.com"
2.生成ssh key
ssh-keygen -t rsa -b 4096 -C "jackson0585@163.com"
3.设置ssh key
打开文件
C:\Users\Administrator.ssh\id_rsa.pub
拷贝里面的内容
打开这个链接
https://github.com/settings/ssh/new
粘贴已拷贝的内容
保存ssh key
4.遇到的问题
如果遇到Fatal: HttpRequestException encountered问题
则下载这个安装包解决:
Git Credential Manager for Windows v1.20
链接:https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases/
git每次提交都需要输入用户名和密码
解决办法:git config --global credential.helper store
下次提交输入用户名和密码后就会记住了
5.让一个项目同时提交到码云和GitHub两个仓库
在项目目录里找到.git文件夹然后找到config文件。
打开这个文件后找到下面的代码
[remote "origin"]
url = git提交地址
fetch = +refs/heads/*:refs/remotes/origin/*
将其改成
[remote "origin"]
url = 码云Git提交地址
url = GitHub提交地址
fetch = +refs/heads/*:refs/remotes/origin/*
问题:
c731c6f..69bae9b master -> master To https://gitee.com/jayh2018/passjava-portal.git ! [rejected] master -> master (fetch first) error: failed to push some refs to 'https://gitee.com/jayh2018/passjava-portal.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
4.8 Windows安装mysql
1.安装截图
2.遇到的问题 1
windows用syslog连接本地mysql数据库,提示 plugin caching_sha2_password
解决方案:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123';
2.遇到的问题 2
Host is not allowed to connect to this MySQL server
使用远程连接mysql的时候碰到这样的错误:
Host is not allowed to connect to this MySQL server。
简单的解决方式如下:
(1)修改表。可能是你的帐号不允许从远程登陆,只能在localhost。这个时候只要在localhost的那台电脑,登入mysql后,更改 "mysql" 数据库里的 "user" 表里的 "host" 项,从"localhost"改称"%"
mysql -u root -p
按照提示输入密码
mysql>use mysql;
mysql>update user set host = '%' where user = 'root';
(2)修改完后执行如下SQL命令
flush privileges
五、PassJava 基础实践篇
5.1 初始化项目和添加微服务
1.GitHub上创建一个空的仓库
2.从GitHub上引入空的项目
3.添加内容服务
passjava-content
序号 | 字段 | 内容 |
---|---|---|
1 | group | com.jackson0714.passjava |
2 | Artifact | passjava-content |
3 | Name | passjava-content |
4 | Description | 佳必过-内容服务 |
5 | Package | com.jackson0714.passjava.content |
添加依赖组件SpringWeb, OpenFeign
3.添加其他微服务
序号 | 服务描述 | 服务名 |
---|---|---|
1 | 内容微服务 | passjava-content |
2 | 会员微服务 | passjava-member |
3 | 题目微服务 | passjava-question |
4 | 学习微服务 | passjava-study |
5 | 渠道微服务 | passjava-channel |
4.PassJava-Platform添加Pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>passjava</name>
<description>佳必过-聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>passjava-content</module>
<module>passjava-member</module>
<module>passjava-question</module>
<module>passjava-study</module>
<module>passjava-channel</module>
</modules>
</project>
5.添加根目录Maven 配置
Maven操作根项目就可以了,试下clean
6. 配置.gitignore文件
提交代码时,忽略某些文件
### gradle ###
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
### STS ###
.settings/
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
rebel.xml
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### maven ###
target/
*.war
*.ear
*.zip
*.tar
*.tar.gz
**/mvnw
**/mvnw.cmd
**/.mvn
### logs ####
/logs/
*.log
### temp ignore ###
*.cache
*.diff
*.patch
*.tmp
*.java~
*.properties~
*.xml~
### system ignore ###
.DS_Store
Thumbs.db
Servers
.metadata
upload
gen_code
### database ###
db/db_back_dir/
### redis ###
/redis/
删除子项目的.gitignore文件
7.提交代码
可以用IDEA的git工具提交,也可以用git bash命令行提交
git add .
git commit -m 'xxx'
git push origin master
5.2 初始化数据库和表
用PowerDisigner工具创建数据库
用PowerDisigner工具创建数据库
总共有5个微服务数据库:内容、学习、渠道、用户、题目
内容微服务的数据库
学习微服务的数据库
渠道微服务的数据库
用户微服务的数据库
题目微服务的数据库
SQL文件在这个项目里面:https://github.com/Jackson0714/PassJava-Platform.git
5.3 搭建管理后台
“管理后台使用人人开源的后台管理框架,完成快速搭建。
”
1.下载人人开源后台管理框架
renren-fast
https://gitee.com/renrenio/renren-fast.git
renren-fast-vue
https://gitee.com/renrenio/renren-fast-vue.git
2.添加人人开源后端代码
PassJava项目
拷贝文件夹renren-fast到PassJava根目录
POM文件 添加依赖
<module>renren-fast</module>
3.初始化后台管理数据库
创建数据库:passjava_admin
执行renren-fast/db/mysql.sql脚本
4.修改renren-fast 服务的配置文件
文件路径:src/main/resources/application-dev.yml
修改数据库连接为自己的mysql数据库连接
5.启动renren-fast服务
配置SDK为1.8
运行renren-fast后台
出现错误:com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large...
解决方案:修改mysql容器的配置文件
cd /mydata/mysql/conf
sudo vim my.cnf
添加配置,[mysqld_safe]如果有,则不需要添加
[mysqld_safe]
max_allowed_packet=32M
执行结果
测试服务运行状态
浏览器输入:http://localhost:8080/renren-fast/
显示结果:
{"msg":"invalid token","code":401}
结果如上所示,则表示服务运行正常。另外结果里面的invalid token说明权限不足,不是指服务不正常。
6.启动前端项目
配置cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装node_modules依赖包
cnpm install
打包前端项目
npm run dev
浏览后台
http://localhost:8002
7.前后端联调登录
登录后台
账号:admin
密码:admin
登录成功
查看后端服务日志
说明前端登录请求发送到了后端服务,并验证了用户名和密码是否正确。
5.4 自动生成前后端代码
1.下载代码生成器框架
git clone https://gitee.com/renrenio/renren-generator.git
2.添加人人开源后端代码
PassJava项目
拷贝文件夹renren-fast到PassJava根目录
POM文件 添加依赖
<module>renren-generator</module>
3.修改renren-generator服务的配置文件
(1)修改数据库链接 src/main/resources/application-dev.yml
修改数据库连接为自己的mysql数据库连接
数据库名改为要生成代码的服务,如passjava_qms数据库
url: jdbc:mysql://129.211.188.xxx:3306/passjava_qms?useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: root
(2)修改属性配置文件 src/main/resources/generator.properties
# 以question微服务为例
mainPath=com.jackson0714
package=com.jackson0714.passjava
moduleName=question
author=jackson0714
email=jackson0585@163.com
tablePrefix=qms_
(3)修改controller 模板文件
src/main/resources/template/Controller.java.vm
删除引入的包,后面再引入
import org.apache.shiro.authz.annotation.RequiresPermissions;
注释RequiresPermissions注解,后面再引入
@RequiresPermissions("${moduleName}:${pathName}:list")
4.启动代码生成器服务
浏览器打开localhost,可以看到数据库qms的两张表已经显示在后台了
5.生成代码
生成代码
拷贝main文件夹到question模块src目录
删除前端代码passjava-question\src\main\resources\src目录
代码结构
生成的代码包含controller,dao层,实体类,接口实现类,mapper映射文件
6.添加common 模块
因为自动生成的代码引用了一些工具类,而我们的项目中没有,所以需要加个common模块添加一些工具类
添加passjava-common
New Module: 选择Maven
Name: passjava-common
7.question模块添加common模块依赖
pom文件添加依赖
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
8.common模块添加依赖
MyBatis-Plus
<!--mybatis-plus DAO层工具 https://mp.baomidou.com/-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.2.0</version>
</dependency>lombok依赖
<!--lombok 不需要写getter,setter方法了-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>httpcore依赖
<!--httpcore 依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.12</version>
</dependency>commons-lang依赖
<!--commons-lang 依赖 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>servlet依赖
<!-- 导入servlet-api 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
9.common模块添加工具类
添加包com.jackson0714.common.utils
从renren-fast项目copy文件
Constans.java
、PageUtils.java
、Query.java
、R.java
、RRException.java
添加包
com.jackson0714.common.xss
从renren-fast项目copy文件
HTMLFilter.java
、SQLFilter.java
5.5 整合MyBatis-Plus实现CRUD
1.添加Mybatis-Plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
2.配置数据源
导入数据库的驱动 查看mysql版本 5.7.29
到maven仓库查看适用的mysql驱动,5.7的没有,8.0兼容5.7的,所以选择8.0的驱动
<!--添加mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
3.配置MyBatis-Plus
添加application.yml 文件配置数据源
文件路径:/passjava-question/src/main/resources/application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.188.xxx:3306/passjava_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: xxx配置mapper映射文件路径
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto添加MapperScan注解
@MapperScan("com.jackson0714.passjava.question.dao")
@SpringBootApplication
public class PassjavaQuestionApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaQuestionApplication.class, args);
}
}
4.测试mybatis-plus的CRUD方法
创建类型为javaBasic的type表数据
@Autowired
TypeService typeService;
// 创建题目类型
@Test
void testCreateType() {
TypeEntity typeEntity = new TypeEntity();
typeEntity.setType("javaBasic");
typeService.save(typeEntity);
System.out.println("创建成功");
}更新id=1的表数据
// 更新type=jvm
@Test
void testUpdateType() {
TypeEntity typeEntity = new TypeEntity();
typeEntity.setId(1L);
typeEntity.setType("jvm");
typeService.updateById(typeEntity);
System.out.println("修改成功");
}查询id=1的表数据
// 查询题目类型
@Test
void testSelectType() {
List<TypeEntity> typeEntityList = typeService.list(new QueryWrapper<TypeEntity>().eq("id",1L));
typeEntityList.forEach((item)-> {
System.out.println(item);
});
System.out.println("查询成功");
}
删除id=1的表数据
// 删除题目类型记录
@Test
void testRemoveType() {
typeService.removeById(1L);
System.out.println("删除成功");
}
5.6 生成所有微服务的CRUD代码
1. 修改代码生成器配置文件
(1)\renren-generator\src\main\resources\generator.properties
mainPath=com.jackson0714
package=com.jackson0714.passjava
moduleName=channel
author=jackson0714
email=jackson0585@163.com
tablePrefix=chms_
(2)\renren-generator\src\main\resources\application.yml
修改连接的数据库:passjava_chms
2.生成渠道微服务代码
启动服务,打开浏览器:http://localhost:8003/#generator.html
注意:端口地址默认是8080,我配置成了8003。
3.添加生成的代码
删除自动生成的代码中的文件夹:main\resources\src 拷贝main文件夹到channel模块src目录下
4.配置渠道微服务
pom.xml引入common模块
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>添加application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://129.211.188.xxx:3306/passjava_chms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: xxx
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto5.测试channel服务接口
访问:http://localhost:8000/channel/channel/list
返回:
{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}
5.生成所有微服务的CRUD代码
6.配置微服务端口
passjava-channel 端口8000
passjava-content 端口9000
passjava-member 端口10000
passjava-question 端口11000
passjava-study 端口12000
所有微服务都启动成功并测试接口通过
http://localhost:8000/channel/channel/list
http://localhost:9000/content/banner/list
http://localhost:10000/member/member/list
http://localhost:11000/question/question/list
http://localhost:12000/study/studytime/list
5.7 管理后台-题目类型功能
1.环境准备
代码准备
将renren-fast-vue代码copy到自己的前端项目中
安装node_module
cnpm install
启动前端portal
npm run dev
登陆后台
1.启动RenrenAplication
2.输入用户名和密码登陆
2. 添加目录和菜单
添加 题目中心
目录(一级菜单)
刷新页面,就可以看到题目中心菜单
添加题目 类型维护
菜单(二级菜单)
可以看到数据库新增了两条记录,分别对应两个菜单
点击类型维护菜单,打开了链接:http://localhost:8002/#/question-type,页面显示空白页面.
3.自动生成前端页面
用renren-generator自动生成前端代码,可以参考这篇:13.SpringCloud实战项目-自动生成前后端代码
拷贝question目录到前端目录 \src\views\modules
4. 测试类型维护功能
点击类型维护菜单,可以看到请求报404
http://localhost:8080/renren-fast/question/type/list?t=1587825969456&page=1&limit=10&key=
“因为页面的请求都访问到renren-fast服务了,所以要修改为访问题目微服务。但是前端有很多请求访问的是不同的服务,所以我们可以通过网关来作为请求的入口,然后将不同的请求路由到不同的服务。
”
SpringCloud整合网关可以看之前写的一篇文章:20.SpringCloud整合Gateway网关
5.配置请求到网关
文件:\static\config\index.js
api接口请求地址替换为gateway的地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8080/renren-fast';
替换为
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8060'; // 网关地址
刷新页面,发现会回到登录页面,而且验证码获取不到,F12调试工具可以看到验证码请求发送到网关上,而网关上找不到这个请求地址(http://localhost:8060/captcha.jpg),所以报404。其实验证码请求应该访问renren-fast服务,所以我们要将验证码请求通过网关转发到renren-fast服务(http://localhost:8080/renren-fast/captcha.jpg)。
# 验证码请求:
GET http://localhost:8060/captcha.jpg?uuid=1ce21f53-1866-40b1-8b20-2f4515d59f0d 404 (Not Found)
“可以将renren-fast注册到注册中心,然后通过网关将请求转发到renren-fast服务。
”
6.注册renren-fast服务
renren-fast项目添加common依赖
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
配置注册中心地址
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
配置应用程序的名称
application:
name: renren-fast
应用类添加
@EnableDiscoveryClient
注解查看服务是否注册成功
7. 添加网关路由规则
配置路由规则
passjava-gateway项目中application.yml文件配置路由规则,并重启passjava-gateway服务
spring:
cloud:
gateway:
routes:
- id: route_portal # 路由规则id
uri: lb://renren-fast # 负载均衡,renren-fast服务
predicates: # 断言
- Path=/api/** # 如果前端请求路径包含 api,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 将访问路径中包含的api替换成renren-fast,但是替换的url不会在前端显示,还是网关的访问路径。这里不是跳转到新的路径,而是转发请求。
修改前端请求路径
文件:\static\config\index.js
请求路径添加api
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8086';
替换为
window.SITE_CONFIG['baseUrl'] = 'http://localhost:8060/api'; // 添加api
刷新登录页面,可以正常获取验证码,请求路径为网关地址 + /api/captcha
http://localhost:8060/api/captcha.jpg?uuid=84d36089-07ae-4201-85c0-8217b032f21b
“前端将请求发送到网关http://localhost:8060/api/captcha.jpg,网关将请求转发到http://localhost:8060/api/renren-fast/captcha.jpg。
”
登录,报跨域问题
Access to XMLHttpRequest at 'http://localhost:8060/api/sys/login' from origin 'http://localhost:8002' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
“登录页面url:http://localhost:8002,点击登录访问的请求url:http://localhost:8060/api/sys/login,两个url的端口号不一样,产生了跨域问题。
”
8.跨域问题
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点 http://domain-a.com 的某 HTML 页面通过 的 src 请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。例如,XMLHttpRequest和Fetch API遵循同源策略。这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
9.解决跨域问题
添加响应头,配置当次请求允许跨域
Access-Control-Allow-Origin:支持哪些来源的请求跨域 Access-Control-Allow-Methods:支持哪些方法跨域 Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie Access-Control-Expose-Headers:跨域请求暴露的字段CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。 Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无 须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果 该首部字段的值超过了最大有效时间,将不会生效。 添加跨域配置
passjava-gateway应用中添加配置类PassJavaCorsConfiguration.java
package com.jackson0714.passjava.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class PassJavaCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 配置跨域
corsConfiguration.addAllowedHeader("*"); // 允许所有请求头跨域
corsConfiguration.addAllowedMethod("*"); // 允许所有请求方法跨域
corsConfiguration.addAllowedOrigin("*"); // 允许所有请求来源跨域
corsConfiguration.setAllowCredentials(true); //允许携带cookie跨域,否则跨域请求会丢失cookie信息
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
注释renren-fast里面的跨域配置
文件路径:src/main/java/io/renren/config/CorsConfig.java
登录成功
可以看到login请求的响应报文中包含了已配置的CORS响应头
10.配置题目服务的路由规则
我们访问题目中心的类型页面,发现还是报404找不到资源
所以我们需要配置题目服务的路由规则,将题目中心的页面请求经网关转发到题目服务。
spring:
cloud:
gateway:
routes:
- id: route_question # 题目微服务路由规则
uri: lb://passjava-question # 负载均衡,将请求转发到注册中心注册的renren-fast服务
predicates: # 断言
- Path=/api/question/** # 如果前端请求路径包含 api/question,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成question
“注意:若predicates的Path更精确,则将路由规则放到更上面,优先命中更上面的路由规则。
”
11.测试类型维护功能
数据库插入3条测试数据
测试查询列表,可以看到有三条记录查询出来了
测试修改一条数据,可以看到数据库里面记录更新为23了
测试删除一条数据,可以看到界面和数据库都删除了一条数据
12.打开新增和批量删除功能
注释权限判断,默认返回true
// src\utils\index.js
/**
* 是否有权限
* @param {*} key
*/
export function isAuth (key) {
// return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
return true
}
5.8 管理后台-题目维护功能
1.配置逻辑删除
所有表字段添加del_flag字段
del_flag tinyint(1) DEFAULT 0 COMMENT '删除标记(0-正常,1-删除)',
MyBatisPlus配置逻辑删除
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-field: delFlag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
log中打印查询SQL语句
SELECT id,type,comments,logo_url,del_flag,create_time,update_time FROM qms_type WHERE del_flag=0
log打印删除SQL语句
UPDATE qms_type SET del_flag=1 WHERE id IN ( 1 ) AND del_flag=0
2.快速显示开关
想要将是否显示改为快速开关
自定义列模板
1.通过
Scoped slot
可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据2.使用Switch开关
Scoped slot:https://element.eleme.cn/#/zh-CN/component/table
Switch开关:https://element.eleme.cn/#/zh-CN/component/switch
<el-table-column prop="enable"
header-align="center"
align="center"
label="是否显示">
<template slot-scope="scope">
<el-switch
v-model="scope.row.enable"
:active-value=1
:inactive-value=0
active-color="#13ce66"
inactive-color="#ff4949"
@change="updateQuestionStatus(scope.row)">
</el-switch>
</template>
</el-table-column>
添加更新方法
// 更新题目是否显示
updateQuestionStatus(data) {
console.log(data)
let {id, enable} = data
this.$http({
url: this.$http.adornUrl('/question/question/update'),
method: 'post',
data: this.$http.adornData({id, enable}, false)
}).then(({ data }) => {
this.$message({
type:"success",
message: "状态更新成功"
})
});
},
3.前端字段校验
对排序字段限制:必须为正整数
dataRule: {
displayOrder: [
{
validator: (rule, value, callback) => {
if (value == "") {
callback(new Error("排序字段必须填写"));
} else if (!Number.isInteger(value) || value<0) {
callback(new Error("排序必须是一个大于等于0的整数"));
} else {
callback();
}
},
trigger: "blur"
}
]
}
4.后端字段校验
实体类字段上添加注解 @Positive
必须是大于0的数字
/**
* 排序
*/
@Positive
private Integer displayOrder;
API 添加注解 @Valid
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody QuestionEntity question){
questionService.save(question);
return R.ok();
}
测试结果
-1,0,0.2 不通过
测试1,1.2通过
5.模糊查询题目列表
修改实现类QuestionServiceImpl
的queryPage
方法
原方法:
public PageUtils queryPage(Map<String, Object> params) {
IPage<QuestionEntity> page = this.page(
new Query<QuestionEntity>().getPage(params),
new QueryWrapper<QuestionEntity>()
);
return new PageUtils(page);
}
修改后:
@Override
public PageUtils queryPage(Map<String, Object> params) {
//1.get key
String key = (String) params.get("key");
QueryWrapper<QuestionEntity> queryWrapper = new QueryWrapper<>();
if (!StringUtils.isEmpty(key)) {
queryWrapper.eq("id", key).or().like("title", key).or().like("answer", key);
}
IPage<QuestionEntity> page = this.page(
new Query<QuestionEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
6.添加分页插件
添加分页插件
package com.jackson0714.passjava.question.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.jackson0714.passjava.question.dao")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
添加分页插件后的显示
六、PassJava 高级实践篇
6.1 Spring Cloud Alibaba 组件简介
1.SpringCloud Alibaba概述
“Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
”
Github:https://github.com/alibaba/spring-cloud-alibaba
Spring Cloud的几大痛点
部分组件停止维护和更新,有问题也不易解决 部分环境搭建起来比较复杂,没有非常友好的可视化界面 配置相对来说复杂,需要较高的学习成本
Spring Cloud Alibaba的优势
阿里经历过了时间的考验 设计合理 拥有不错的可视化界面,方便运维监控和排查问题 环境搭建和配置简单,学习成本低
PassJava项目搭配SpringCloud Alibaba技术的搭配方案
描述 | Spring Cloud | Spring Cloud Alibaba | 组合选用 |
---|---|---|---|
服务发现组件 | Eureka(停止维护)服务发现组件 | Nacos 注册中心 | Spring Cloud Alibaba - Nacos |
配置中心组件 | Spring Cloud Config 配置中心 | Nacos 配置中心 | Spring Cloud Alibaba - Nacos |
断路保护组件 | Hystrix 断路保护 | Sentinel 服务容错 | Spring Cloud Alibaba - Sentinel |
链路追踪组件 | Sleuth 调用链监控 | / | Spring Cloud - Sleuth |
负载均衡组件 | Ribbon | / | Spring Cloud - Ribbon |
远程调用组件 | OpenFeign (HTTP+JSON) | Dubbo(RPC框架) | Spring Cloud - OpenFeign |
分布式事务 | / | Seata 分布式事务 | Spring Cloud Alibaba - Seata |
API 网关 | Gateway | / | Spring Cloud - Gateway |
最后技术选型:
Spring Cloud Alibaba - Nacos 实现注册中心
Spring Cloud Alibaba - Nacos 实现配置中心
Spring Cloud Alibaba - Sentinel 实现服务容错
Spring Cloud Alibaba - Seata 实现分布式事务
Spring Cloud - Ribbon 实现负载均衡
Spring Cloud - Feign 实现远程调用
Spring Cloud - Gateway API网关
Spring Cloud - Sleuth 实现调用链监控
2.Spring Cloud Alibaba版本
项目的版本号格式为 x.x.x 的形式,其中 x 的数值类型为数字,从 0 开始取值,且不限于 0~9 这个范围。项目处于孵化器阶段时,第一位版本号固定使用 0,即版本号为 0.x.x 的格式。
由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且 spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此阿里采取跟 SpringBoot 版本号一致的版本:
1.5.x 版本适用于 Spring Boot 1.5.x 2.0.x 版本适用于 Spring Boot 2.0.x 2.1.x 版本适用于 Spring Boot 2.1.x 2.2.x 版本适用于 Spring Boot 2.2.x
Spring Cloud Alibaba 版本和Spring Cloud 和Spring Boot 版本兼容性列表
Spring Cloud 版本 | Spring Cloud Alibaba 版本 | Spring Boot 版本 |
---|---|---|
Spring Cloud Hoxton.SR3 | 2.2.x.RELEASE | 2.2.x.RELEASE |
Spring Cloud Greenwich | 2.1.x.RELEASE | 2.1.x.RELEASE |
Spring Cloud Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE |
Spring Cloud Edgware | 1.5.x.RELEASE | 1.5.x.RELEASE |
我们采用Spring Cloud Hoxton.SR3
, Spring Cloud Alibaba 2.2.0.RELEASE
, Spring Boot 2.2.6 RELEASE
PassJava-Common的pom.xml文件引入Spring Cloud Alibaba依赖
<dependencyManagement>
<dependencies>
<!-- Spring Cloud Alibaba 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
6.2 SpringCloud整合Alibaba-Nacos组件
“Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
”
1.引入Nacos 服务发现组件
passjava-common模块的pom.xml文件引入Nacos 服务发现组件
<!-- nacos discovery 服务发现组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.下载Nacos Server并启动
下载Nacos Server 压缩包
https://github.com/alibaba/nacos/releases
启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。
Linux/Unix/Mac 操作系统,执行命令 sh startup.sh -m standalone
Windows 操作系统,执行命令 cmd startup.cmd
windows执行startupm.cmd遇到问题:
λ startup.cmd
Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!
解决方案:
修改startup.cmd文件中的%JAVA_HOME%
%JAVA_HOME% 替换为 C:\Program Files\Java\jdk1.8.0_131
启动成功:
3.每个微服务都配置Nacos Server 地址
配置Nacos Server 地址
在passjava-question、passjava-channel、passjava-content、passjava-member、passjava-study 应用的 /src/main/resources/application.yml配置文件中配置 Nacos Server 地址
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
4.添加注解
为每个服务使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
@EnableDiscoveryClient
@MapperScan("com.jackson0714.passjava.question.dao")
@SpringBootApplication
public class PassjavaQuestionApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaQuestionApplication.class, args);
}
}
5.配置微服务的名称
spring:
application:
name: passjava-question
6.访问nacos server后台
登录后台
http://localhost:8848/nacos/index.html#/login
用户名:nacos
密码:nacos
查看已注册的服务
passjava-channel 渠道微服务
passjava-member 用户微服务
passjava-study 学习微服务
passjava-question 问题微服务
passjava-content 内容微服务
6.3 SpringCloud整合OpenFeign远程调用
1.Feign 概述
Feign声明式客的HTTP客户端,让远程调用更简单。 提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息 整合了Ribbon(负载均衡组件)和Hystix(服务熔断组件),不需要显示使用这两个组件 Spring Cloud Feign 在Netflix Feign的基础上扩展了对SpringMVC注解的支持
2. 远程调用示例
“示例:查询用户的学习时长
”
用户微服务passjava-member调用学习微服务passjava-study的方法
2.1 引入openfeign依赖
passjava-member和passjava-study项目的pom文件引入openfeign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 StudyTimeController定义远程调用测试方法
返回某个用户学习题目的总时长
@RequestMapping("/member/list/test")
public R memberStudyTimeTest() {
StudyTimeEntity studyTimeEntity = new StudyTimeEntity();
studyTimeEntity.setTotalTime(100); // 学习时长:100分钟
studyTimeEntity.setQuesTypeId(1L); // 题目类型:1 (javaBasic)
return R.ok().put("studyTime", Arrays.asList(studyTimeEntity));
}
2.3 member目录下创建feign service
创建package: com.jackson0714.passjava.member.feign
创建StudyTimeFeignService接口
添加注解
@FeignClient
。显示声明这个接口用来远程调用study
服务。@FeignClient("passjava-study")
public interface StudyTimeFeignService {}添加远程调用方法
public R memberStudyTime();
给方法添加要远程调用的方法的路径
study/studytime/member/list/test
@RequestMapping("study/studytime/member/list/test")
public R getMemberStudyTimeListTest();添加注解
@EnableFeignClients
开启远程调用服务。给类PassjavaStudyApplication.java添加注解
@EnableFeignClients
。basePackages代表自动扫码指定路径下所有带有@FeignClient注解的接口。
@EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign")
@EnableDiscoveryClient
@MapperScan("com.jackson0714.passjava.member.dao")
@SpringBootApplication
public class PassjavaMemberApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaMemberApplication.class, args);
}
}测试接口
studytime和member都有数据,学习时长:100分钟,昵称:悟空聊架构
启动passjava-member和passjava-study服务
用postman工具或浏览器输入请求地址
http://localhost:10000/member/member/studytime/list/test
返回结果如下图
2.4 测试OpenFeign传参
示例:用户id作为参数在服务间传递
MemberController
@RequestMapping("/studytime/list/test/{id}")
public R getMemberStudyTimeListTest(@PathVariable("id") Long id) {
//mock数据库查到的会员信息
MemberEntity memberEntity = new MemberEntity();
memberEntity.setId(id); // 学习时长:100分钟
memberEntity.setNickname("悟空聊架构");
//远程调用拿到该用户的学习时长(学习时长是mock数据)
R memberStudyTimeList = studyTimeFeignService.getMemberStudyTimeListTest(id);
return R.ok().put("member", memberEntity).put("studytime", memberStudyTimeList.get("studytime"));
}
StudyTimeFeignService
@FeignClient("passjava-study")
public interface StudyTimeFeignService {
@RequestMapping("study/studytime/member/list/test/{id}")
public R getMemberStudyTimeListTest(@PathVariable("id") Long id);
}
StudyTimeController
@RequestMapping("/member/list/test/{id}")
public R memberStudyTimeTest(@PathVariable("id") Long id) {
StudyTimeEntity studyTimeEntity = new StudyTimeEntity();
studyTimeEntity.setTotalTime(100); // 学习时长:100分钟
studyTimeEntity.setQuesTypeId(1L); // 题目类型:1 (javaBasic)
return R.ok().put("studytime", Arrays.asList(studyTimeEntity));
}
请求地址和参数:http://localhost:10000/member/member/studytime/list/test/1
执行结果:
2.5 总结FeignClient使用方法
引入OpenFeign依赖 定义FeignClient接口类(注解 @FeignClient
),声明这个接口类是用来远程调用其他服务的接口类中定义要远程调用的接口方法,指定远程服务方法的路径 Controller类中调用接口方法 开启远程调用(注解 @EnableFeignClients
)远程调用的流程: @RequestBody将这个对象转为json 找到passjava-study服务,给study/studytime/member/list/test服务发送请求 将json放到请求体里面,发送请求 对方服务收到请求,请求体里有json数据 将请求体中的json数据转换成对方服务的参数类型。只需要两边的字段名称和类型是一致的。
6.4 Spring Cloud 整合 Nacos配置中心
1.传统配置方式
application.properties文件中定义两个配置:
member.nickname = "悟空聊架构"
member.age = "18"
示例控制器中定义私有变量nickname和age,@value代表从配置中取值
@Value("${member.nickname}")
private String nickname;
@Value("$member.age")
private Integer age;
示例控制器中定义方法:获取nick和age的值
@RequestMapping("/test-local-config")
public R testLocalConfig() {
return R.ok().put("nickname", nickname).put("age", age);
}
测试结果
总结:从配置文件中获取配置。
这种方式的缺点是什么呢?如果要修改配置参数,则需要重新启动服务。如果服务很多,则需要重启所有服务,非常不方便。
有没有什么办法不停服务修改配置而且使其生效呢?
答案:有的,用Spring Cloud Alibaba的Nacos 组件就可以完成。
2.引入Nacos依赖
PassJava-Common项目的pom.xml文件引入Spring Cloud Alibaba Nacos Config依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
3.配置Nacos元数据
passjava-member 添加 /src/main/resources/bootstrap.properties 配置文件(注意:bootstrap.properties 优先级高于其他配置文件)
配置 Nacos Config 元数据
spring.application.name=passjava-member
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
4.Nacos后台新增配置
Data ID: passjava-member.properties
Group: DEFAULT_GROUP
配置格式:
member.nick="悟空"
member.age=10
5.开启动态刷新配置功能
添加注解@RefreshScope开启动态刷新配置功能
@RefreshScope
@RestController
@RequestMapping("member/sample")
public class SampleController {}
可以从控制台看到日志信息:
Refresh keys changed: [member.age]
2020-04-19 23:34:07.154 INFO 8796 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-ok] dataId=passjava-member.properties, group=DEFAULT_GROUP, md5=df136e146c83cbf857567e75acb11e2b, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@4f49b78b
2020-04-19 23:34:07.154 INFO 8796 --- [-127.0.0.1_8848] c.a.nacos.client.config.impl.CacheData : [fixed-127.0.0.1_8848] [notify-listener] time cost=529ms in ClientWorker, dataId=passjava-member.properties, group=DEFAULT_GROUP, md5=df136e146c83cbf857567e75acb11e2b, listener=com.alibaba.cloud.nacos.refresh.NacosContextRefresher$1@4f49b78b
member.age
更新了,通知了member服务,刷新了配置。对应的配置id为passjava-member.properties
,分组为DEFAULT_GROUP
。监听器为com.alibaba.cloud.nacos.refresh.NacosContextRefresher
6.测试结果
访问:http://localhost:10000/member/sample/test-local-config
结果:nickname和age和Nacos后台配置一致
结论:只用在Nacos后台改配置即可实时修改配置。
注意:Nacos的配置项优先级高于application.propertite里面的配置。
7.命名空间
我们现在有5个微服务,每个微服务用到的配置可能都不一样,那不同微服务怎么样获取自己微服务的配置呢?
这里可以用到命名空间,我们针对每个微服务,都创建一个命名空间。
创建命名空间
# 创建5个命名空间
passjava-channel
passjava-content
passjava-member
passjava-question
passjava-study
命名空间下创建配置
我们打开配置列表菜单,可以看到有五个命名空间。
选中passjava-channel命名空间,然后新增配置项,与之前新增配置的步骤一致,也可以通过克隆命名空间来克隆配置。
修改指定的命名空间
bootstrap.properties配置命名空间
spring.cloud.nacos.config.namespace=passjava-member
测试配置是否生效
修改passjava-member.properties的配置内容
重启member服务
访问方法:/member/sample/test-local-config
执行结果:
{
"msg": "success",
"code": 0,
"nickname": "\"悟空member\"",
"age": 30
}说明获取的是passjava-member命名空间的配置
8.分组
如果我们有多套环境,比如开发环境,测试环境,生产环境,每一套环境的配置参数不一样,那配置中心该如何配置呢?
我们可以使用配置中心的分组
功能。每一套环境都是一套分组。
首先创建一套dev环境配置项,然后克隆配置到test和prod环境
bootstrap.properties配置当前使用的分组:prod
spring.cloud.nacos.config.group=prod
测试获取生产环境配置
{
"msg": "success",
"code": 0,
"nickname": "\"悟空-prod\"",
"age": 10
}可以看到获取到的是prod分组的配置
9.多配置集
我们可以将application.yml文件中的datasource、mybatis-plus等配置进行拆解,放到配置中心。group可以创建3套,dev/test/prod。
1.配置中心新建datasource.yml
配置
2.配置中心新建mybatis.yml
配置
3.配置中心新建more.yml
配置
4.克隆dev环境配置到test和prod环境
5.bootstrap.properties增加nacos配置,application.yml注释配置
spring.application.name=passjava-member
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=passjava-member
spring.cloud.nacos.config.group=prod
spring.cloud.nacos.config.extension-configs[0].data-id=datasource.yml
spring.cloud.nacos.config.extension-configs[0].group=dev
spring.cloud.nacos.config.extension-configs[0].refresh=true
spring.cloud.nacos.config.extension-configs[1].data-id=mybatis.yml
spring.cloud.nacos.config.extension-configs[1].group=dev
spring.cloud.nacos.config.extension-configs[1].refresh=true
spring.cloud.nacos.config.extension-configs[2].data-id=more.yml
spring.cloud.nacos.config.extension-configs[2].group=dev
spring.cloud.nacos.config.extension-configs[2].refresh=true
6.测试配置是否生效
测试passjava-member.properties和more.yml配置是否生效
请求url:http://localhost:10000/member/sample/test-local-config
返回配置的nick和age,且端口是10000,且member服务注册到注册中心
{
"msg": "success",
"code": 0,
"nickname": "\"悟空-prod1\"",
"age": 22
}
测试datasource.yml和mybatis.yml配置是否生效
请求url:http://localhost:10000/member/member/list
返回数据库查询结果
{
"msg": "success",
"code": 0,
"page": {
"totalCount": 0,
"pageSize": 10,
"totalPage": 0,
"currPage": 1,
"list": []j
}
}
说明以上配置都生效了。
更多配置项
配置项 | key | 默认值 | 说明 |
---|---|---|---|
服务端地址 | spring.cloud.nacos.config.server-addr | ||
DataId前缀 | spring.cloud.nacos.config.prefix | spring.application.name | |
Group | spring.cloud.nacos.config.group | DEFAULT_GROUP | |
dataID后缀及内容文件格式 | spring.cloud.nacos.config.file-extension | properties | dataId的后缀,同时也是配置内容的文件格式,目前只支持 properties |
配置内容的编码方式 | spring.cloud.nacos.config.encode | UTF-8 | 配置的编码 |
获取配置的超时时间 | spring.cloud.nacos.config.timeout | 3000 | 单位为 ms |
配置的命名空间 | spring.cloud.nacos.config.namespace | 常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源隔离等。 | |
AccessKey | spring.cloud.nacos.config.access-key | ||
SecretKey | spring.cloud.nacos.config.secret-key | ||
相对路径 | spring.cloud.nacos.config.context-path | 服务端 API 的相对路径 | |
接入点 | spring.cloud.nacos.config.endpoint | UTF-8 | 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址 |
是否开启监听和自动刷新 | spring.cloud.nacos.config.refresh-enabled | true |
10.使用Nacos总结
1.引入Nacos依赖 2.配置Nacos数据源 3.配置中心配置数据集 DataId
和配置内容4.开启动态刷新配置 @RefreshScope
5.获取配置项的值 @value
6.优先使用配置中心的配置 7.使用命名空间 namespace
来创建各服务的配置8.使用分组 group
来区分不同环境9.使用多配置集 extension-configs
区分不同类型的配置
6.5 SpringCloud整合Gateway网关
1.Gateway网关介绍
网关:流量的入口 网关常用功能:路由转发,权限校验,限流控制 Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架 Spring Cloud Gateway取代了netflix的Zuul网关
2.Gateway原理
PassJava项目中,小程序和管理后台请求先访问到API网关.
API网关通过注册中心实时感知微服务的状态的路由地址,准确地将请求路由到各个服务.
官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/
请求到达网关后,先经过断言Predicate,是否符合某个路由规则
如果符合,则按路由规则路由到指定地址
请求和响应都可以通过过滤器Filter进行过滤
3.创建Gateway 模块
适用Spring 初始化器创建Gateway module
创建module
选择Gateway依赖
引入Gateway模块
<module>passjava-gateway</module>
4.配置Gateway
引入Nacos组件
因common模块引入了nacos注册中心组件,所以我们可以直接引用common模块
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
应用类上添加注解 @EnableDiscoveryClient
@RefreshScope
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class PassjavaGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaGatewayApplication.class, args);
}
}
5.使用Gateway demo
新建application.yml文件
spring:
cloud:
gateway:
routes:
- id: route_qq
uri: http://www.qq.com
predicates:
- Query=url,qq
- id: route_baidu
uri: http://www.baidu.com
predicates:
- Query=url,baidu第一条路由规则:当请求路径中包含url=qq,则跳转到http://www.qq.com
第二条路由规则:当请求路径中包含url=baidu,则跳转到http://www.baidu.com
后续在PassJava项目中使用Gateway的强大功能.
6.6 整合OSS对象存储
1.缘起
“文件上传在系统中用的很频繁,所以我们需要将上传的文件进行存储,传统的将文件上传到本机已不适用分布式系统。自己搭建文件服务器有复杂性和维护成本。所以我们可以采用市面上成熟的文件存储服务,如阿里云的OSS对象存储服务。
”
每个 OSS 的用户都会用到上传服务。Web 端常见的上传方法是用户在浏览器或 APP 端上传文件到应用服务器,应用服务器再把文件上传到 OSS。具体流程如下图所示。
和数据直传到 OSS 相比,以上方法有三个缺点:
上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。
2.技术方案
2.1 服务端签名后直传
2.1.1 背景
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。
2.1.2 原理介绍
服务端签名后直传的原理如下:
用户发送上传Policy请求到应用服务器。 应用服务器返回上传Policy和签名给用户。 用户直接上传数据到OSS。
3.实现案例
3.1 开通阿里云OSS
登录阿里云官网
https://www.aliyun.com/sale-season/2020/procurement-new-members?userCode=thp9caen
创建Bucket 存储桶
获取accesskey id和secret
分配权限
分配 管理对象存储服务(OSS)权限
3.2 使用OSS SDK
3.2.1 安装SDK
在Maven项目中加入依赖项
https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.769.2c5145dc4TUgTa
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
3.2.2 上传文件到OSS
@Test
void testUploadByOss() throws FileNotFoundException {
// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint = "http://oss-cn-beijing.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId = "xxxx";
String accessKeySecret = "xxxx";
String bucketName = "passjava";
// <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
String localFile = "C:\\Users\\Administrator\\Pictures\\coding_java.png";
String fileKeyName = "coding_java.png";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
InputStream inputStream = new FileInputStream(localFile);
ossClient.putObject(bucketName, fileKeyName, inputStream);
// 关闭OSSClient。
ossClient.shutdown();
}
3.3 整合Spring Cloud Alicloud OSS
3.3.1 passjava-common项目引入spring-cloud-starter-alicloud-oss依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
3.3.2 配置alicloud oss
spring:
cloud:
alicloud:
access-key: xxxx
secret-key: xxxx
oss:
endpoint: oss-cn-beijing.aliyuncs.com
3.3.3 测试上传
@Autowired
OSSClient ossClient;
@Test
void testUploadByAlicloudOss() throws FileNotFoundException {
String bucketName = "passjava";
String localFile = "C:\\Users\\Administrator\\Pictures\\coding_java.png";
String fileKeyName = "coding_java.png";
InputStream inputStream = new FileInputStream(localFile);
ossClient.putObject(bucketName, fileKeyName, inputStream);
ossClient.shutdown();
}
3.4 获取服务端签名
3.4.1 准备工作:
创建一个第三方服务passjava-thirdparty 引入passjava-common模块,并且排除mybatis-plus依赖
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
配置服务发现和端口
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: passjava-thirdparty
server:
port: 14000
配置配置中心
spring.application.name=passjava-thirdparty
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=passjava-thirdparty
spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
配置Nacos命名空间和oss.yml
spring:
cloud:
alicloud:
access-key: LTAI4G3KxBJ26EUbWsenmqhP
secret-key: RHtADVlvlKJvVBQnFNNvnne9p4NwnA
oss:
endpoint: oss-cn-beijing.aliyuncs.com
开启服务发现 @EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication
public class PassjavaThirdpartyApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaThirdpartyApplication.class, args);
}
}
3.4.2 获取签名类
@RestController
@RequestMapping("/thirdparty/v1/admin/oss")
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.secret-key}")
private String accessKey;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@RequestMapping("/getPolicy")
public Map<String, String> getPolicy() {
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
// String callbackUrl = "http://88.88.88.88:8888";
String formatDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = formatDate + "/"; // 用户上传文件时指定的前缀。
Map<String, String> respMap = new LinkedHashMap<String, String>();
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return respMap;
}
}
测试接口
http://localhost:14000/api/thirdparty/v1/admin/oss/getPolicy
{
"accessid": "LTAI4G3KxBJ26EUbWsenmqhP",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMC0wNC0yOFQwMjozMzowNy42NzNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIwLTA0LTI4LyJdXX0=",
"signature": "pfn4cggFTMMNqTs+qUnDN5c+k5M=",
"dir": "2020-04-28/",
"host": "https://passjava.oss-cn-beijing.aliyuncs.com",
"expire": "1588041187"
}
3.4.3 配置网关路由
因为前端页面配置的统一访问路径是http://localhost:8060/api/,所以需要将访问thirdparty的服务通过网关路由到thirdparty服务
将请求
http://localhost:8060/api/thirdparty/v1/admin/oss/getPolicy
转发到
http://localhost:14000/api/thirdparty/v1/admin/oss/getPolicy
配置网关:
spring:
cloud:
gateway:
routes:
- id: route_thirdparty # 题目微服务路由规则
uri: lb://passjava-thirdparty # 负载均衡,将请求转发到注册中心注册的assjava-thirdparty服务
predicates: # 断言
- Path=/api/thirdparty/** # 如果前端请求路径包含 api/thirdparty,则应用这条路由规则
filters: #过滤器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 将跳转路径中包含的api替换成空
测试可以上传成功
3.4.4 配置跨域访问
配置跨域访问,所有post请求都可以跨域访问
3.4.5 Web端上传组件
单文件上传组件
singleUpload.vue
<template>
<div>
<el-upload
action="http://passjava.oss-cn-beijing.aliyuncs.com"
:data="dataObj"
list-type="picture"
:multiple="false" :show-file-list="showFileList"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="fileList[0].url" alt="">
</el-dialog>
</div>
</template>
<script>
import {policy} from './policy'
import { getUUID } from '@/utils'
export default {
name: 'singleUpload',
props: {
value: String
},
computed: {
imageUrl() {
return this.value;
},
imageName() {
if (this.value != null && this.value !== '') {
return this.value.substr(this.value.lastIndexOf("/") + 1);
} else {
return null;
}
},
fileList() {
return [{
name: this.imageName,
url: this.imageUrl
}]
},
showFileList: {
get: function () {
return this.value !== null && this.value !== ''&& this.value!==undefined;
},
set: function (newValue) {
}
}
},
data() {
return {
dataObj: {
policy: '',
signature: '',
key: '',
ossaccessKeyId: '',
dir: '',
host: '',
// callback:'',
},
dialogVisible: false
};
},
methods: {
emitInput(val) {
this.$emit('input', val)
},
handleRemove(file, fileList) {
this.emitInput('');
},
handlePreview(file) {
this.dialogVisible = true;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy().then(response => {
_self.dataObj.policy = response.data.policy;
_self.dataObj.signature = response.data.signature;
_self.dataObj.ossaccessKeyId = response.data.accessid;
_self.dataObj.key = response.data.dir + getUUID()+'_${filename}';
_self.dataObj.dir = response.data.dir;
_self.dataObj.host = response.data.host;
resolve(true)
}).catch(err => {
reject(false)
})
})
},
handleUploadSuccess(res, file) {
console.log("上传成功...")
this.showFileList = true;
this.fileList.pop();
this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
this.emitInput(this.fileList[0].url);
}
}
}
</script>
<style>
</style>
获取签名的JS文件
import http from '@/utils/httpRequest.js'
export function policy () {
return new Promise((resolve) => {
http({
url: http.adornUrl('/thirdparty/v1/admin/oss/getPolicy'),
method: 'get',
params: http.adornParams({})
}).then(({ data }) => {
resolve(data)
})
})
}
使用单文件上传组件
使用上传图片组件
<el-form-item label="类型logo路径" prop="logoUrl">
<single-upload v-model="dataForm.logoUrl"></single-upload>
</el-form-item>
<script>
import SingleUpload from "@/components/upload/singleUpload" // 引入单文件上传组件
export default {
components:{ SingleUpload }
}
</script>
上传文件成功
6.7 整合统一异常处理
1.缘起
“我们在写代码的时候,通常会在方法里面添加各种try catch来捕获异常,会发现有很多重复的代码,所以我们可以整合统一异常处理来优化代码结构。
”
拦截异常并统一处理我们可以用到@RestControllerAdvice
注解
2.自定义异常处理类
添加统一异常处理类注解
@RestControllerAdvice
添加日志注解
@Slf4j
添加异常处理方法注解
@ExceptionHandler
package com.jackson0714.passjava.question.exception;
/*
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.jackson0714.passjava.question.controller")
public class PassjavaExceptionControllerAdvice {
@ResponseBody
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(), BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data", errorMap);
}
@ExceptionHandler(value=Throwable.class)
public R handleException(Throwable throwable) {
log.error("未知异常{},异常类型:{}", throwable.getMessage(), throwable.getClass());
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
}
3.推荐的系统错误码
3.1 错误码和错误信息定义类
1.错误码长度:5个数字
2.前两位:业务场景
3.后三位:错误码
10:通用业务
001:参数格式校验错误(10001)
11:会员业务
12:题目业务
13:内容业务
14:学习业务
3.2 错误码枚举类
com.jackson0714.common.exception.BizCodeEnum
定义了两种异常枚举:系统未知异常、参数格式校验失败
package com.jackson0714.common.exception;
public enum BizCodeEnum {
UNKNOWN_EXCEPTION(10000, "系统未知异常"),
VALID_EXCEPTION(10001, "参数格式校验失败");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
4.测试代码
“测试场景1:校验参数displayOrder必须为正整数,如果displayOrder不为正整数,则会抛出异常
”
1.实体类上添加校验注解 @Positive
/**
* 排序
*/
@Positive
private Integer displayOrder;
2.controller类里面添加save方法,并添加校验参数注解@Valid
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody QuestionEntity question){
questionService.save(question);
return R.ok();
}
测试:
用Postman工具调用save方法
请求地址:
http://192.168.10.160:8060/api/question/v1/admin/question/save
请求参数:
{
"displayOrder": 0.2
}
返回结果:
{
"msg": "参数格式校验失败",
"code": 10001,
"data": {
"displayOrder": "必须是正数"
}
}
“测试场景2:对于代码里面直接抛出的异常,也可以handle
”
1.controller类里面添加查询题目的方法,并抛出Exception异常
/**
* 信息
*/
@RequestMapping("/info/{id}")
//@RequiresPermissions("question:question:info")
public R info(@PathVariable("id") Long id) throws Exception {
QuestionEntity question = questionService.getById(id);
throw new Exception("test");
//return R.ok().put("question", question);
}
测试:
用Postman工具调用save方法
请求地址:
http://192.168.10.160:8060/api/question/v1/admin/question/save
返回结果:
{
"msg": "系统未知异常",
"code": 10000
}
证明统一处理方法被调用了:
@ExceptionHandler(value=Throwable.class)
public R handleException(Throwable throwable) {
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
- END -
点亮,服务器三年不宕机